import { Cartesian3 } from '../../Source/Cesium.js';
import { Color } from '../../Source/Cesium.js';
import { defined } from '../../Source/Cesium.js';
import { PixelFormat } from '../../Source/Cesium.js';
import { Resource } from '../../Source/Cesium.js';
import { ClearCommand } from '../../Source/Cesium.js';
import { ContextLimits } from '../../Source/Cesium.js';
import { CubeMap } from '../../Source/Cesium.js';
import { PixelDatatype } from '../../Source/Cesium.js';
import { Sampler } from '../../Source/Cesium.js';
import { Texture } from '../../Source/Cesium.js';
import { TextureMagnificationFilter } from '../../Source/Cesium.js';
import { TextureMinificationFilter } from '../../Source/Cesium.js';
import { TextureWrap } from '../../Source/Cesium.js';
import createContext from '../createContext.js';
import { when } from '../../Source/Cesium.js';

describe('Renderer/CubeMap', function() {

    var context;
    var cubeMap;

    function expectCubeMapFaces(options) {
        var cubeMap = options.cubeMap;
        var expectedColors = options.expectedColors;

        var fs =
            'uniform samplerCube u_texture;' +
            'uniform mediump vec3 u_direction;' +
            'void main() { gl_FragColor = textureCube(u_texture, normalize(u_direction)); }';

        var faceDirections = options.faceDirections;
        if (!defined(faceDirections)) {
            faceDirections = [
                new Cartesian3(1.0, 0.0, 0.0),  // +X
                new Cartesian3(-1.0, 0.0, 0.0), // -X
                new Cartesian3(0.0, 1.0, 0.0),  // +Y
                new Cartesian3(0.0, -1.0, 0.0), // -Y
                new Cartesian3(0.0, 0.0, 1.0),  // +Z
                new Cartesian3(0.0, 0.0, -1.0)  // -Z
            ];
        }

        var uniformMap = {
            direction : undefined,

            u_texture : function() {
                return cubeMap;
            },
            u_direction : function() {
                return this.direction;
            }
        };

        for (var i = 0; i < 6; ++i) {
            uniformMap.direction = faceDirections[i];
            expect({
                context : context,
                fragmentShader : fs,
                uniformMap : uniformMap,
                epsilon : options.epsilon
            }).contextToRender(expectedColors[i]);
        }
    }

    var greenImage;
    var blueImage;
    var blueAlphaImage;
    var blueOverRedImage;
    var red16x16Image;

    beforeAll(function() {
        context = createContext();

        var promises = [];
        promises.push(Resource.fetchImage('./Data/Images/Green.png').then(function(result) {
            greenImage = result;
        }));
        promises.push(Resource.fetchImage('./Data/Images/Blue.png').then(function(result) {
            blueImage = result;
        }));
        promises.push(Resource.fetchImage('./Data/Images/BlueAlpha.png').then(function(result) {
            blueAlphaImage = result;
        }));
        promises.push(Resource.fetchImage('./Data/Images/BlueOverRed.png').then(function(result) {
            blueOverRedImage = result;
        }));
        promises.push(Resource.fetchImage('./Data/Images/Red16x16.png').then(function(result) {
            red16x16Image = result;
        }));

        return when.all(promises);
    });

    afterAll(function() {
        context.destroyForSpecs();
    });

    afterEach(function() {
        cubeMap = cubeMap && cubeMap.destroy();
    });

    it('gets the pixel format', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });

        expect(cubeMap.pixelFormat).toEqual(PixelFormat.RGBA);
        expect(cubeMap.positiveX.pixelFormat).toEqual(PixelFormat.RGBA);
        expect(cubeMap.negativeX.pixelFormat).toEqual(PixelFormat.RGBA);
        expect(cubeMap.positiveY.pixelFormat).toEqual(PixelFormat.RGBA);
        expect(cubeMap.negativeY.pixelFormat).toEqual(PixelFormat.RGBA);
        expect(cubeMap.positiveZ.pixelFormat).toEqual(PixelFormat.RGBA);
        expect(cubeMap.negativeZ.pixelFormat).toEqual(PixelFormat.RGBA);
    });

    it('gets the pixel datatype', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });

        expect(cubeMap.pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
        expect(cubeMap.positiveX.pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
        expect(cubeMap.negativeX.pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
        expect(cubeMap.positiveY.pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
        expect(cubeMap.negativeY.pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
        expect(cubeMap.positiveZ.pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
        expect(cubeMap.negativeZ.pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE);
    });

    it('sets a sampler', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });

        var sampler = new Sampler({
            wrapS : TextureWrap.REPEAT,
            wrapT : TextureWrap.MIRRORED_REPEAT,
            minificationFilter : TextureMinificationFilter.NEAREST,
            magnificationFilter : TextureMagnificationFilter.NEAREST
        });
        cubeMap.sampler = sampler;

        var s = cubeMap.sampler;
        expect(s.wrapS).toEqual(sampler.wrapS);
        expect(s.wrapT).toEqual(sampler.wrapT);
        expect(s.minificationFilter).toEqual(sampler.minificationFilter);
        expect(s.magnificationFilter).toEqual(sampler.magnificationFilter);
    });

    it('gets width and height', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });

        expect(cubeMap.width).toEqual(16);
        expect(cubeMap.height).toEqual(16);
    });

    it('gets size in bytes', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });

        expect(cubeMap.sizeInBytes).toEqual(256 * 4 * 6);
    });

    it('gets flip Y', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16,
            flipY : true
        });

        expect(cubeMap.flipY).toEqual(true);
    });

    it('draws with a cube map', function() {
        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : blueImage,
                negativeX : greenImage,
                positiveY : blueImage,
                negativeY : greenImage,
                positiveZ : blueImage,
                negativeZ : greenImage
            }
        });

        expectCubeMapFaces({
            cubeMap : cubeMap,
            expectedColors : [
                [0, 0, 255, 255], // +X is blue
                [0, 255, 0, 255], // -X is green
                [0, 0, 255, 255], // +Y is blue
                [0, 255, 0, 255], // -Y is green
                [0, 0, 255, 255], // +Z is blue
                [0, 255, 0, 255]  // -Z is green
            ]
        });
    });

    it('draws with a cube map with premultiplied alpha', function() {
        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : blueAlphaImage,
                negativeX : blueAlphaImage,
                positiveY : blueAlphaImage,
                negativeY : blueAlphaImage,
                positiveZ : blueAlphaImage,
                negativeZ : blueAlphaImage
            },
            preMultiplyAlpha : true
        });
        expect(cubeMap.preMultiplyAlpha).toEqual(true);

        expectCubeMapFaces({
            cubeMap : cubeMap,
            epsilon : 1,
            expectedColors : [
                [0, 0, 127, 255], // +X
                [0, 0, 127, 255], // -X
                [0, 0, 127, 255], // +Y
                [0, 0, 127, 255], // -Y
                [0, 0, 127, 255], // +Z
                [0, 0, 127, 255]  // -Z
            ]
        });
    });

    it('draws the context default cube map', function() {
        expectCubeMapFaces({
            cubeMap : context.defaultCubeMap,
            expectedColors : [
                [255, 255, 255, 255], // +X
                [255, 255, 255, 255], // -X
                [255, 255, 255, 255], // +Y
                [255, 255, 255, 255], // -Y
                [255, 255, 255, 255], // +Z
                [255, 255, 255, 255]  // -Z
            ]
        });
    });

    it('creates a cube map with typed arrays', function() {
        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([0, 255, 255, 255])
                },
                negativeX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([0, 0, 255, 255])
                },
                positiveY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([0, 255, 0, 255])
                },
                negativeY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([255, 0, 0, 255])
                },
                positiveZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([255, 0, 255, 255])
                },
                negativeZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([255, 255, 0, 255])
                }
            }
        });

        expectCubeMapFaces({
            cubeMap : cubeMap,
            expectedColors : [
                [0, 255, 255, 255], // +X
                [0, 0, 255, 255],   // -X
                [0, 255, 0, 255],   // +Y
                [255, 0, 0, 255],   // -Y
                [255, 0, 255, 255], // +Z
                [255, 255, 0, 255]  // -Z
            ]
        });
    });

    it('creates a cube map with floating-point textures', function() {
        if (!context.floatingPointTexture) {
            return;
        }

        var positiveXColor = new Color(0.0, 1.0, 1.0, 1.0);
        var negativeXColor = new Color(0.0, 0.0, 1.0, 1.0);
        var positiveYColor = new Color(0.0, 1.0, 0.0, 1.0);
        var negativeYColor = new Color(1.0, 0.0, 0.0, 1.0);
        var positiveZColor = new Color(1.0, 0.0, 1.0, 1.0);
        var negativeZColor = new Color(1.0, 1.0, 0.0, 1.0);

        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([positiveXColor.red, positiveXColor.green, positiveXColor.blue, positiveXColor.alpha])
                },
                negativeX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([negativeXColor.red, negativeXColor.green, negativeXColor.blue, negativeXColor.alpha])
                },
                positiveY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([positiveYColor.red, positiveYColor.green, positiveYColor.blue, positiveYColor.alpha])
                },
                negativeY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([negativeYColor.red, negativeYColor.green, negativeYColor.blue, negativeYColor.alpha])
                },
                positiveZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([positiveZColor.red, positiveZColor.green, positiveZColor.blue, positiveZColor.alpha])
                },
                negativeZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([negativeZColor.red, negativeZColor.green, negativeZColor.blue, negativeZColor.alpha])
                }
            },
            pixelDatatype : PixelDatatype.FLOAT
        });

        expectCubeMapFaces({
            cubeMap : cubeMap,
            expectedColors : [
                [0, 255, 255, 255], // +X
                [0, 0, 255, 255],   // -X
                [0, 255, 0, 255],   // +Y
                [255, 0, 0, 255],   // -Y
                [255, 0, 255, 255], // +Z
                [255, 255, 0, 255]  // -Z
            ]
        });
    });

    it('creates a cube map with floating-point textures and linear filtering', function() {
        if (!context.floatingPointTexture) {
            return;
        }

        var positiveXColor = new Color(0.0, 1.0, 1.0, 1.0);
        var negativeXColor = new Color(0.0, 0.0, 1.0, 1.0);
        var positiveYColor = new Color(0.0, 1.0, 0.0, 1.0);
        var negativeYColor = new Color(1.0, 0.0, 0.0, 1.0);
        var positiveZColor = new Color(1.0, 0.0, 1.0, 1.0);
        var negativeZColor = new Color(1.0, 1.0, 0.0, 1.0);

        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([positiveXColor.red, positiveXColor.green, positiveXColor.blue, positiveXColor.alpha])
                },
                negativeX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([negativeXColor.red, negativeXColor.green, negativeXColor.blue, negativeXColor.alpha])
                },
                positiveY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([positiveYColor.red, positiveYColor.green, positiveYColor.blue, positiveYColor.alpha])
                },
                negativeY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([negativeYColor.red, negativeYColor.green, negativeYColor.blue, negativeYColor.alpha])
                },
                positiveZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([positiveZColor.red, positiveZColor.green, positiveZColor.blue, positiveZColor.alpha])
                },
                negativeZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Float32Array([negativeZColor.red, negativeZColor.green, negativeZColor.blue, negativeZColor.alpha])
                }
            },
            pixelDatatype : PixelDatatype.FLOAT,
            sampler : new Sampler({
                wrapS : TextureWrap.CLAMP_TO_EDGE,
                wrapT : TextureWrap.CLAMP_TO_EDGE,
                minificationFilter : TextureMinificationFilter.LINEAR,
                magnificationFilter : TextureMagnificationFilter.LINEAR
            })
        });

        var fs =
            'uniform samplerCube u_texture;' +
            'void main() { gl_FragColor = textureCube(u_texture, normalize(vec3(1.0, 1.0, 0.0))); }';

        var uniformMap = {
            u_texture : function() {
                return cubeMap;
            }
        };

        if (!context.textureFloatLinear) {
            expect({
                context : context,
                fragmentShader : fs,
                uniformMap : uniformMap,
                epsilon : 1
            }).contextToRender(positiveYColor.toBytes());
        } else {
            Color.multiplyByScalar(positiveXColor, 1.0 - 0.5, positiveXColor);
            Color.multiplyByScalar(positiveYColor, 0.5, positiveYColor);
            var color = Color.add(positiveXColor, positiveYColor, positiveXColor);
            expect({
                context : context,
                fragmentShader : fs,
                uniformMap : uniformMap,
                epsilon : 1
            }).contextToRender(color.toBytes());
        }
    });

    it('creates a cube map with half floating-point textures', function() {
        if (!context.halfFloatingPointTexture) {
            return;
        }

        var positiveXFloats = [12902, 13926, 14541, 15360];
        var negativeXFloats = [13926, 12902, 14541, 15360];
        var positiveYFloats = [14541, 13926, 12902, 15360];
        var negativeYFloats = [12902, 14541, 13926, 15360];
        var positiveZFloats = [13926, 14541, 12902, 15360];
        var negativeZFloats = [14541, 12902, 13926, 15360];

        var positiveXColor = new Color(0.2, 0.4, 0.6, 1.0);
        var negativeXColor = new Color(0.4, 0.2, 0.6, 1.0);
        var positiveYColor = new Color(0.6, 0.4, 0.2, 1.0);
        var negativeYColor = new Color(0.2, 0.6, 0.4, 1.0);
        var positiveZColor = new Color(0.4, 0.6, 0.2, 1.0);
        var negativeZColor = new Color(0.6, 0.2, 0.4, 1.0);

        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(positiveXFloats)
                },
                negativeX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(negativeXFloats)
                },
                positiveY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(positiveYFloats)
                },
                negativeY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(negativeYFloats)
                },
                positiveZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(positiveZFloats)
                },
                negativeZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(negativeZFloats)
                }
            },
            pixelDatatype : PixelDatatype.HALF_FLOAT
        });

        expectCubeMapFaces({
            cubeMap : cubeMap,
            expectedColors : [
                positiveXColor.toBytes(),
                negativeXColor.toBytes(),
                positiveYColor.toBytes(),
                negativeYColor.toBytes(),
                positiveZColor.toBytes(),
                negativeZColor.toBytes()
            ]
        });
    });

    it('creates a cube map with half floating-point textures and linear filtering', function() {
        if (!context.halfFloatingPointTexture) {
            return;
        }

        var positiveXFloats = [12902, 13926, 14541, 15360];
        var negativeXFloats = [13926, 12902, 14541, 15360];
        var positiveYFloats = [14541, 13926, 12902, 15360];
        var negativeYFloats = [12902, 14541, 13926, 15360];
        var positiveZFloats = [13926, 14541, 12902, 15360];
        var negativeZFloats = [14541, 12902, 13926, 15360];

        var positiveXColor = new Color(0.2, 0.4, 0.6, 1.0);
        var positiveYColor = new Color(0.6, 0.4, 0.2, 1.0);

        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(positiveXFloats)
                },
                negativeX : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(negativeXFloats)
                },
                positiveY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(positiveYFloats)
                },
                negativeY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(negativeYFloats)
                },
                positiveZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(positiveZFloats)
                },
                negativeZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint16Array(negativeZFloats)
                }
            },
            pixelDatatype : PixelDatatype.HALF_FLOAT,
            sampler : new Sampler({
                wrapS : TextureWrap.CLAMP_TO_EDGE,
                wrapT : TextureWrap.CLAMP_TO_EDGE,
                minificationFilter : TextureMinificationFilter.LINEAR,
                magnificationFilter : TextureMagnificationFilter.LINEAR
            })
        });

        var fs =
            'uniform samplerCube u_texture;' +
            'void main() { gl_FragColor = textureCube(u_texture, normalize(vec3(1.0, 1.0, 0.0))); }';

        var uniformMap = {
            u_texture : function() {
                return cubeMap;
            }
        };

        if (!context.textureHalfFloatLinear) {
            expect({
                context : context,
                fragmentShader : fs,
                uniformMap : uniformMap,
                epsilon : 1
            }).contextToRender(positiveYColor.toBytes());
        } else {
            Color.multiplyByScalar(positiveXColor, 1.0 - 0.5, positiveXColor);
            Color.multiplyByScalar(positiveYColor, 0.5, positiveYColor);
            var color = Color.add(positiveXColor, positiveYColor, positiveXColor);
            expect({
                context : context,
                fragmentShader : fs,
                uniformMap : uniformMap,
                epsilon : 1
            }).contextToRender(color.toBytes());
        }
    });

    it('creates a cube map with typed arrays and images', function() {
        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : blueImage,
                negativeX : greenImage,
                positiveY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([0, 255, 0, 255])
                },
                negativeY : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([255, 0, 0, 255])
                },
                positiveZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([0, 0, 255, 255])
                },
                negativeZ : {
                    width : 1,
                    height : 1,
                    arrayBufferView : new Uint8Array([255, 255, 0, 255])
                }
            }
        });

        expectCubeMapFaces({
            cubeMap : cubeMap,
            expectedColors : [
                [0, 0, 255, 255],   // +X
                [0, 255, 0, 255],   // -X
                [0, 255, 0, 255],   // +Y
                [255, 0, 0, 255],   // -Y
                [0, 0, 255, 255],   // +Z
                [255, 255, 0, 255]  // -Z
            ]
        });
    });

    it('copies to a cube map', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1
        });
        cubeMap.positiveX.copyFrom(blueImage);
        cubeMap.negativeX.copyFrom(greenImage);
        cubeMap.positiveY.copyFrom(blueImage);
        cubeMap.negativeY.copyFrom(greenImage);
        cubeMap.positiveZ.copyFrom(blueImage);
        cubeMap.negativeZ.copyFrom(greenImage);

        expectCubeMapFaces({
            cubeMap : cubeMap,
            expectedColors : [
                [0, 0, 255, 255], // +X
                [0, 255, 0, 255], // -X
                [0, 0, 255, 255], // +Y
                [0, 255, 0, 255], // -Y
                [0, 0, 255, 255], // +Z
                [0, 255, 0, 255]  // -Z
            ]
        });
    });

    it('copies from a typed array', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1
        });
        cubeMap.positiveX.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([0, 255, 255, 255])
        });
        cubeMap.negativeX.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([0, 0, 255, 255])
        });
        cubeMap.positiveY.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([0, 255, 0, 255])
        });
        cubeMap.negativeY.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([255, 0, 0, 255])
        });
        cubeMap.positiveZ.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([255, 0, 255, 255])
        });
        cubeMap.negativeZ.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([255, 255, 0, 255])
        });

        expectCubeMapFaces({
            cubeMap : cubeMap,
            expectedColors : [
                [0, 255, 255, 255], // +X
                [0, 0, 255, 255],   // -X
                [0, 255, 0, 255],   // +Y
                [255, 0, 0, 255],   // -Y
                [255, 0, 255, 255], // +Z
                [255, 255, 0, 255]  // -Z
            ]
        });
    });

    it('sub copies images to a cube map', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 2,
            height : 2
        });
        cubeMap.positiveX.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([0, 255, 255, 255])
        });
        cubeMap.negativeX.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([0, 0, 255, 255])
        });
        cubeMap.positiveY.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([0, 255, 0, 255])
        });
        cubeMap.negativeY.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([255, 0, 0, 255])
        });
        cubeMap.positiveZ.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([255, 0, 255, 255])
        });
        cubeMap.negativeZ.copyFrom({
            width : 1,
            height : 1,
            arrayBufferView : new Uint8Array([0, 255, 0, 255])
        }, 1, 0);

        var negativeZDirection = new Cartesian3(0.25, 0.0, -1.0);
        Cartesian3.normalize(negativeZDirection, negativeZDirection);

        expectCubeMapFaces({
            cubeMap : cubeMap,
            expectedColors : [
                [0, 64, 64, 255], // +X
                [0, 0, 64, 255], // -X
                [0, 64, 0, 255], // +Y
                [64, 0, 0, 255], // -Y
                [64, 0, 64, 255], // +Z
                [0, 32, 0, 255]  // -Z
            ],
            faceDirections : [
                new Cartesian3(1.0, 0.0, 0.0),  // +X
                new Cartesian3(-1.0, 0.0, 0.0), // -X
                new Cartesian3(0.0, 1.0, 0.0),  // +Y
                new Cartesian3(0.0, -1.0, 0.0), // -Y
                new Cartesian3(0.0, 0.0, 1.0),  // +Z
                negativeZDirection  // -Z
            ]
        });
    });

    it('sub copies array buffers to a cube map', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 2,
            height : 2
        });
        cubeMap.positiveX.copyFrom(blueImage);
        cubeMap.negativeX.copyFrom(greenImage);
        cubeMap.positiveY.copyFrom(blueImage);
        cubeMap.negativeY.copyFrom(greenImage);
        cubeMap.positiveZ.copyFrom(blueImage);
        cubeMap.negativeZ.copyFrom(greenImage, 1, 0);

        var negativeZDirection = new Cartesian3(0.25, 0.0, -1.0);
        Cartesian3.normalize(negativeZDirection, negativeZDirection);

        expectCubeMapFaces({
            cubeMap : cubeMap,
            expectedColors : [
                [0, 0, 64, 255], // +X
                [0, 64, 0, 255], // -X
                [0, 0, 64, 255], // +Y
                [0, 64, 0, 255], // -Y
                [0, 0, 64, 255], // +Z
                [0, 32, 0, 255]  // -Z
            ],
            faceDirections : [
                new Cartesian3(1.0, 0.0, 0.0),  // +X
                new Cartesian3(-1.0, 0.0, 0.0), // -X
                new Cartesian3(0.0, 1.0, 0.0),  // +Y
                new Cartesian3(0.0, -1.0, 0.0), // -Y
                new Cartesian3(0.0, 0.0, 1.0),  // +Z
                negativeZDirection  // -Z
            ]
        });
    });

    it('copies from the framebuffer', function() {
        var cxt = createContext({
            webgl : {
                alpha : true // Seems to be required for copyFromFramebuffer()
            }
        });

        cubeMap = new CubeMap({
            context : cxt,
            width : 1,
            height : 1
        });
        cubeMap.positiveX.copyFrom(blueImage);

        var fs =
            'uniform samplerCube u_cubeMap;' +
            'void main() { gl_FragColor = textureCube(u_cubeMap, vec3(1.0, 0.0, 0.0)); }';

        var uniformMap = {
            u_cubeMap : function() {
                return cubeMap;
            }
        };

        // +X is blue
        expect({
            context : cxt,
            fragmentShader : fs,
            uniformMap : uniformMap
        }).contextToRender([0, 0, 255, 255]);

        // Clear framebuffer to red and copy to +X face
        var clearCommand = new ClearCommand({
            color : new Color (1.0, 0.0, 0.0, 1.0)
        });

        clearCommand.execute(cxt);
        expect(cxt).toReadPixels([255, 0, 0, 255]);
        cubeMap.positiveX.copyFromFramebuffer();

        ClearCommand.ALL.execute(cxt);
        expect(cxt).toReadPixels([0, 0, 0, 0]);

        // +X is red now
        expect({
            context : cxt,
            fragmentShader : fs,
            uniformMap : uniformMap
        }).contextToRender([255, 0, 0, 255]);

        cxt.destroyForSpecs();
    });

    it('draws with a cube map and a texture', function() {
        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : greenImage,
                negativeX : greenImage,
                positiveY : greenImage,
                negativeY : greenImage,
                positiveZ : greenImage,
                negativeZ : greenImage
            }
        });

        var texture = new Texture({
            context : context,
            source : blueImage
        });

        var fs =
            'uniform samplerCube u_cubeMap;' +
            'uniform sampler2D u_texture;' +
            'void main() { gl_FragColor = textureCube(u_cubeMap, vec3(1.0, 0.0, 0.0)) + texture2D(u_texture, vec2(0.0)); }';

        var uniformMap = {
            u_cubeMap : function() {
                return cubeMap;
            },
            u_texture : function() {
                return texture;
            }
        };

        expect({
            context : context,
            fragmentShader : fs,
            uniformMap : uniformMap
        }).contextToRender([0, 255, 255, 255]);

        texture = texture.destroy();
    });

    it('generates mipmaps', function() {
        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : blueImage,
                negativeX : greenImage,
                positiveY : blueImage,
                negativeY : greenImage,
                positiveZ : blueImage,
                negativeZ : greenImage
            }
        });

        cubeMap.generateMipmap();
        cubeMap.sampler = new Sampler({
            minificationFilter : TextureMinificationFilter.NEAREST_MIPMAP_LINEAR
        });

        var fs =
            'uniform samplerCube u_cubeMap;' +
            'void main() { gl_FragColor = textureCube(u_cubeMap, vec3(1.0, 0.0, 0.0)); }';

        var uniformMap = {
            u_cubeMap : function() {
                return cubeMap;
            }
        };

        expect({
            context : context,
            fragmentShader : fs,
            uniformMap : uniformMap
        }).contextToRender([0, 0, 255, 255]);
    });

    it('gets size in bytes for mipmap', function() {
        cubeMap = new CubeMap({
            context : context,
            source : {
                positiveX : red16x16Image,
                negativeX : red16x16Image,
                positiveY : red16x16Image,
                negativeY : red16x16Image,
                positiveZ : red16x16Image,
                negativeZ : red16x16Image
            }
        });
        cubeMap.generateMipmap();

        // Allow for some leniency with the sizeInBytes approximation
        expect(cubeMap.sizeInBytes).toEqualEpsilon((16*16 + 8*8 + 4*4 + 2*2 + 1) * 4 * 6, 10);
    });

    it('destroys', function() {
        var c = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });

        expect(c.isDestroyed()).toEqual(false);
        c.destroy();
        expect(c.isDestroyed()).toEqual(true);
    });

    it('fails to create (options)', function() {
        expect(function() {
            cubeMap = new CubeMap();
        }).toThrowDeveloperError();
    });

    it('fails to create (source)', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (width, no height)', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context,
                width : 16
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (width != height)', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context,
                width : 16,
                height : 32
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (small width)', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context,
                width : 0,
                height : 0
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (large width)', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context,
                width : ContextLimits.maximumCubeMapSize + 1,
                height : ContextLimits.maximumCubeMapSize + 1
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (PixelFormat)', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context,
                width : 16,
                height : 16,
                pixelFormat : 'invalid PixelFormat'
            });
        }).toThrowDeveloperError();
    });

    it('throws during creation if pixel format is depth or depth-stencil', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context,
                width : 16,
                height : 16,
                pixelFormat : PixelFormat.DEPTH_COMPONENT
            });
        }).toThrowDeveloperError();
    });

    it('throws during creation if pixelDatatype is FLOAT, and OES_texture_float is not supported', function() {
        if (!context.floatingPointTexture) {
            expect(function() {
                cubeMap = new CubeMap({
                    context : context,
                    width : 16,
                    height : 16,
                    pixelDatatype : PixelDatatype.FLOAT
                });
            }).toThrowDeveloperError();
        }
    });

    it('throws during creation if pixelDatatype is HALF_FLOAT, and OES_texture_half_float is not supported', function() {
        if (!context.halfFloatingPointTexture) {
            expect(function() {
                cubeMap = new CubeMap({
                    context : context,
                    width : 16,
                    height : 16,
                    pixelDatatype : PixelDatatype.HALF_FLOAT
                });
            }).toThrowDeveloperError();
        }
    });

    it('fails to create (pixelDatatype)', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context,
                width : 16,
                height : 16,
                pixelFormat : PixelFormat.RGBA,
                pixelDatatype : 'invalid pixelDatatype'
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (source)', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context,
                source : {}
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (source width and height)', function() {
        expect(function() {
            cubeMap = new CubeMap({
                context : context,
                source : {
                    positiveX : greenImage, // 1x1
                    negativeX : greenImage, // 1x1
                    positiveY : greenImage, // 1x1
                    negativeY : greenImage, // 1x1
                    positiveZ : greenImage, // 1x1
                    negativeZ : blueOverRedImage // 1x2
                }
            });
        }).toThrowDeveloperError();
    });

    it('fails to copy from an image (source)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });

        expect(function() {
            cubeMap.positiveX.copyFrom();
        }).toThrowDeveloperError();
    });

    it('fails to copy from an image (xOffset)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });
        var image = new Image();

        expect(function() {
            cubeMap.positiveY.copyFrom(image, -1);
        }).toThrowDeveloperError();
    });

    it('fails to copy from an image (yOffset)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });
        var image = new Image();

        expect(function() {
            cubeMap.positiveZ.copyFrom(image, 0, -1);
        }).toThrowDeveloperError();
    });

    it('fails to copy from an image (width)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });
        var image = new Image();
        image.width = 16 + 1;

        expect(function() {
            cubeMap.negativeX.copyFrom(image);
        }).toThrowDeveloperError();
    });

    it('fails to copy from an image (height)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });
        var image = new Image();
        image.height = 16 + 1;

        expect(function() {
            cubeMap.negativeY.copyFrom(image);
        }).toThrowDeveloperError();
    });

    it('fails to copy from the framebuffer (invalid data type)', function() {
        if (context.floatingPointTexture) {
            cubeMap = new CubeMap({
                context : context,
                width : 1,
                height : 1,
                pixelDatatype : PixelDatatype.FLOAT
            });

            expect(function() {
                cubeMap.positiveX.copyFromFramebuffer();
            }).toThrowDeveloperError();
        }
    });

    it('fails to copy from the framebuffer (xOffset)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1
        });

        expect(function() {
            cubeMap.positiveX.copyFromFramebuffer(-1);
        }).toThrowDeveloperError();
    });

    it('fails to copy from the framebuffer (yOffset)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1
        });

        expect(function() {
            cubeMap.positiveY.copyFromFramebuffer(0, -1);
        }).toThrowDeveloperError();
    });

    it('fails to copy from the framebuffer (framebufferXOffset)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1
        });

        expect(function() {
            cubeMap.positiveZ.copyFromFramebuffer(0, 0, -1);
        }).toThrowDeveloperError();
    });

    it('fails to copy from the framebuffer (framebufferYOffset)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1
        });

        expect(function() {
            cubeMap.negativeX.copyFromFramebuffer(0, 0, 0, -1);
        }).toThrowDeveloperError();
    });

    it('fails to copy from the framebuffer (width)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1
        });

        expect(function() {
            cubeMap.negativeY.copyFromFramebuffer(0, 0, 0, 0, cubeMap.width + 1);
        }).toThrowDeveloperError();
    });

    it('fails to copy from the framebuffer (height)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1
        });

        expect(function() {
            cubeMap.negativeZ.copyFromFramebuffer(0, 0, 0, 0, 0, cubeMap.height + 1);
        }).toThrowDeveloperError();
    });

    it('fails to copy from the framebuffer (FLOAT', function() {
        if (!context.floatingPointTexture) {
            return;
        }

        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1,
            pixelDatatype : PixelDatatype.FLOAT
        });

        expect(function() {
            cubeMap.negativeX.copyFromFramebuffer(0, 0, 0, 0, 0, 0);
        }).toThrowDeveloperError();
    });

    it('fails to copy from the framebuffer (HALF_FLOAT', function() {
        if (!context.halfFloatingPointTexture) {
            return;
        }

        cubeMap = new CubeMap({
            context : context,
            width : 1,
            height : 1,
            pixelDatatype : PixelDatatype.HALF_FLOAT
        });

        expect(function() {
            cubeMap.negativeX.copyFromFramebuffer(0, 0, 0, 0, 0, 0);
        }).toThrowDeveloperError();
    });

    it('fails to generate mipmaps (width)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 3,
            height : 3
        });

        expect(function() {
            cubeMap.generateMipmap();
        }).toThrowDeveloperError();
    });

    it('fails to generate mipmaps (hint)', function() {
        cubeMap = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });

        expect(function() {
            cubeMap.generateMipmap('invalid hint');
        }).toThrowDeveloperError();
    });

    it('fails to destroy', function() {
        var c = new CubeMap({
            context : context,
            width : 16,
            height : 16
        });
        c.destroy();

        expect(function() {
            c.destroy();
        }).toThrowDeveloperError();
    });
}, 'WebGL');
